/*
 * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_private/usb_phy.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "usb_frame.h"

#if CONFIG_IDF_TARGET_ESP32P4
#include "driver/jpeg_decode.h"
#endif

#define USB_FRAME_QUEUE_NUM_MAX 3 // 帧队列长度

static frame_t * frame_buffers[USB_FRAME_QUEUE_NUM_MAX]={NULL, NULL, NULL};
// 定义空帧缓冲区和已填充帧缓冲区的队列句柄
static QueueHandle_t empty_fb_queue = NULL;  // 存储空闲帧缓冲区的队列
static QueueHandle_t filled_fb_queue = NULL; // 存储已填充数据的帧缓冲区的队列
static const char *TAG = "usb_frame";        // 日志标签

/**
 * @brief 分配指定数量的帧缓冲区
 * @param nb_of_fb 需要分配的帧缓冲区数量
 * @param fb_size 每个帧缓冲区的大小（字节）
 * @return esp_err_t 返回操作结果，ESP_OK表示成功，其他表示失败
 */
esp_err_t frame_allocate( size_t fb_size)
{
    esp_err_t ret = ESP_OK; // 默认返回成功

    // 创建用于传递帧缓冲区引用的队列
    // 创建空帧缓冲区队列，队列项是指向frame_t的指针
    empty_fb_queue = xQueueCreate(USB_FRAME_QUEUE_NUM_MAX, sizeof(frame_t *));
    ESP_RETURN_ON_FALSE(empty_fb_queue, ESP_ERR_NO_MEM, TAG, "queues memory not enough %d", USB_FRAME_QUEUE_NUM_MAX);

    // 创建已填充帧缓冲区队列，队列项是指向frame_t的指针
    filled_fb_queue = xQueueCreate(USB_FRAME_QUEUE_NUM_MAX, sizeof(frame_t *));
    ESP_RETURN_ON_FALSE(filled_fb_queue, ESP_ERR_NO_MEM, TAG, "queues memory not enough %d", USB_FRAME_QUEUE_NUM_MAX);

    // 为每个帧缓冲区分配内存
    for (int i = 0; i < USB_FRAME_QUEUE_NUM_MAX; i++) {
        // 分配帧缓冲区结构体
        frame_buffers[i] = malloc(sizeof(frame_t));
        ESP_RETURN_ON_FALSE(frame_buffers[i], ESP_ERR_NO_MEM, TAG, "frame memory not enough %d", fb_size);

        // 根据不同芯片平台分配帧数据内存
#if CONFIG_IDF_TARGET_ESP32P4
        // ESP32P4平台使用JPEG解码器专用内存分配
        size_t malloc_size = 0;
        jpeg_decode_memory_alloc_cfg_t tx_mem_cfg = {
            .buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER, // 分配输入缓冲区内存
        };
        uint8_t *this_data = (uint8_t *)jpeg_alloc_decoder_mem(fb_size, &tx_mem_cfg, &malloc_size);
#elif CONFIG_IDF_TARGET_ESP32S3
        // ESP32S3平台使用SPIRAM内存，16字节对齐
        uint8_t *this_data = (uint8_t *)heap_caps_aligned_alloc(16, fb_size, MALLOC_CAP_SPIRAM);
#endif

        // 检查内存分配是否成功
        if (!frame_buffers[i]) {
            free(frame_buffers[i]); // 释放已分配的帧结构体
            ret = ESP_ERR_NO_MEM;
            ESP_LOGE(TAG, "frame memory not enough %d", fb_size);
            break; // 跳出循环
        }

        // 初始化帧缓冲区成员
        frame_buffers[i]->data = this_data; // 设置数据指针
        frame_buffers[i]->data_buffer_len = fb_size; // 设置缓冲区总长度
        frame_buffers[i]->data_len = 0;              // 初始化已使用长度为0

        // 将帧缓冲区添加到空帧队列
        const BaseType_t result = xQueueSend(empty_fb_queue, &frame_buffers[i], 0);
        assert(pdPASS == result); // 确保添加成功
    }
    return ret;
}

esp_err_t frame_free(void)
{
    for(int i = 0; i < USB_FRAME_QUEUE_NUM_MAX; i++)
    {
        if (frame_buffers[i])
        {
            // 释放帧数据内存
            heap_caps_aligned_free(frame_buffers[i]->data);
            free(frame_buffers[i]);
            frame_buffers[i] = NULL;
        }
    }
    return ESP_OK;
}

/**
 * @brief 重置帧缓冲区，将已使用长度设为0
 * @param frame 需要重置的帧缓冲区指针
 */
void frame_reset(frame_t *frame)
{
    assert(frame);       // 确保帧指针有效
    frame->data_len = 0; // 重置已使用长度为0
}

/**
 * @brief 将帧缓冲区返回到空帧队列
 * @param frame 需要返回的帧缓冲区指针
 * @return esp_err_t 返回操作结果
 */
esp_err_t frame_return_empty(frame_t *frame)
{
    frame_reset(frame);                                        // 先重置帧缓冲区
    BaseType_t result = xQueueSend(empty_fb_queue, &frame, 0); // 发送到空帧队列
    ESP_RETURN_ON_FALSE(result == pdPASS, ESP_ERR_NO_MEM, TAG, "queue memory not enough");
    return ESP_OK;
}

/**
 * @brief 将帧缓冲区发送到已填充帧队列
 * @param frame 已填充数据的帧缓冲区指针
 * @return esp_err_t 返回操作结果
 */
esp_err_t frame_send_filled(frame_t *frame)
{
    BaseType_t result = xQueueSend(filled_fb_queue, &frame, 0); // 发送到已填充帧队列
    ESP_RETURN_ON_FALSE(result == pdPASS, ESP_ERR_NO_MEM, TAG, "queue memory not enough");
    return ESP_OK;
}

/**
 * @brief 向帧缓冲区添加数据
 * @param frame 目标帧缓冲区指针
 * @param data 要添加的数据指针
 * @param data_len 要添加的数据长度
 * @return esp_err_t 返回操作结果
 */
esp_err_t frame_add_data(frame_t *frame, const uint8_t *data, size_t data_len)
{
    // 参数检查
    ESP_RETURN_ON_FALSE(frame && data && data_len, ESP_ERR_INVALID_ARG, TAG, "invalid argument");

    // 检查缓冲区是否足够容纳新数据
    if (frame->data_buffer_len < frame->data_len + data_len)
    {
        ESP_LOGD(TAG, "frame buffer not enough");
        return ESP_ERR_INVALID_SIZE; // 返回大小错误
    }

    // 将数据复制到帧缓冲区
    memcpy(frame->data + frame->data_len, data, data_len);
    frame->data_len += data_len; // 更新已使用长度
    return ESP_OK;
}

/**
 * @brief 从空帧队列获取一个帧缓冲区
 * @return frame_t* 返回获取到的帧缓冲区指针，如果没有可用缓冲区则返回NULL
 */
frame_t *frame_get_empty(void)
{
    frame_t *this_fb;
    // 非阻塞方式从空帧队列获取缓冲区
    if (xQueueReceive(empty_fb_queue, &this_fb, 0) == pdPASS)
    {
        return this_fb;
    }
    else
    {
        return NULL; // 没有可用缓冲区
    }
}

/**
 * @brief 从已填充帧队列获取一个帧缓冲区
 * @return frame_t* 返回获取到的帧缓冲区指针，如果没有可用缓冲区则阻塞等待
 */
frame_t *frame_get_filled(void)
{
    frame_t *this_fb;
    // 阻塞方式从已填充帧队列获取缓冲区（无限等待）
    if (xQueueReceive(filled_fb_queue, &this_fb, portMAX_DELAY) == pdPASS)
    {
        return this_fb;
    }
    else
    {
        return NULL; // 正常情况下不会执行到这里
    }
}